/**
 * $Id: mxEdgeStyle.java,v 1.36 2010-08-02 13:43:11 david Exp $
 * Copyright (c) 2007, Gaudenz Alder
 */
package com.mxgraph.view;

import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxUtils;

import java.util.List;

/**
 * Provides various edge styles to be used as the values for
 * mxConstants.STYLE_EDGE in a cell style. Alternatevly, the mxConstants.
 * EDGESTYLE_* constants can be used to reference an edge style via the
 * mxStyleRegistry.
 */
public class mxEdgeStyle {

    /**
     * Defines the requirements for an edge style function.
     */
    public interface mxEdgeStyleFunction {

        /**
         * Implements an edge style function. At the time the function is called, the result
         * array contains a placeholder (null) for the first absolute point,
         * that is, the point where the edge and source terminal are connected.
         * The implementation of the style then adds all intermediate waypoints
         * except for the last point, that is, the connection point between the
         * edge and the target terminal. The first ant the last point in the
         * result array are then replaced with mxPoints that take into account
         * the terminal's perimeter and next point on the edge.
         *
         * @param state  Cell state that represents the edge to be updated.
         * @param source Cell state that represents the source terminal.
         * @param target Cell state that represents the target terminal.
         * @param points List of relative control points.
         * @param result Array of points that represent the actual points of the
         *               edge.
         */
        void apply(mxCellState state, mxCellState source, mxCellState target,
                   List<mxPoint> points, List<mxPoint> result);

    }

    /**
     * Provides an entity relation style for edges (as used in database
     * schema diagrams).
     */
    public static mxEdgeStyleFunction EntityRelation = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.mxgraph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, java.util.List, java.util.List)
           */
        public void apply(mxCellState state, mxCellState source,
                          mxCellState target, List<mxPoint> points, List<mxPoint> result) {
            mxGraphView view = state.getView();
            mxIGraphModel model = view.getGraph().getModel();
            double segment = mxUtils.getDouble(state.getStyle(),
                    mxConstants.STYLE_SEGMENT, mxConstants.ENTITY_SEGMENT)
                    * state.view.getScale();

            mxPoint p0 = state.getAbsolutePoint(0);
            mxPoint pe = state
                    .getAbsolutePoint(state.getAbsolutePointCount() - 1);

            boolean isSourceLeft = false;

            if (p0 != null) {
                source = new mxCellState();
                source.setX(p0.getX());
                source.setY(p0.getY());
            } else if (source != null) {
                mxGeometry sourceGeometry = model.getGeometry(source.cell);

                if (sourceGeometry.isRelative()) {
                    isSourceLeft = sourceGeometry.getX() <= 0.5;
                } else if (target != null) {
                    isSourceLeft = target.getX() + target.getWidth() < source
                            .getX();
                }
            }

            boolean isTargetLeft = true;

            if (pe != null) {
                target = new mxCellState();
                target.setX(pe.getX());
                target.setY(pe.getY());
            } else if (target != null) {
                mxGeometry targetGeometry = model.getGeometry(target.cell);

                if (targetGeometry.isRelative()) {
                    isTargetLeft = targetGeometry.getX() <= 0.5;
                } else if (source != null) {
                    isTargetLeft = source.getX() + source.getWidth() < target
                            .getX();
                }
            }

            if (source != null && target != null) {
                double x0 = (isSourceLeft) ? source.getX() : source.getX()
                        + source.getWidth();
                double y0 = view.getRoutingCenterY(source);

                double xe = (isTargetLeft) ? target.getX() : target.getX()
                        + target.getWidth();
                double ye = view.getRoutingCenterY(target);

                double seg = segment;

                double dx = (isSourceLeft) ? -seg : seg;
                mxPoint dep = new mxPoint(x0 + dx, y0);
                result.add(dep);

                dx = (isTargetLeft) ? -seg : seg;
                mxPoint arr = new mxPoint(xe + dx, ye);

                // Adds intermediate points if both go out on same side
                if (isSourceLeft == isTargetLeft) {
                    double x = (isSourceLeft) ? Math.min(x0, xe) - segment
                            : Math.max(x0, xe) + segment;
                    result.add(new mxPoint(x, y0));
                    result.add(new mxPoint(x, ye));
                } else if ((dep.getX() < arr.getX()) == isSourceLeft) {
                    double midY = y0 + (ye - y0) / 2;
                    result.add(new mxPoint(dep.getX(), midY));
                    result.add(new mxPoint(arr.getX(), midY));
                }

                result.add(arr);
            }
        }
    };

    /**
     * Provides a self-reference, aka. loop.
     */
    public static mxEdgeStyleFunction Loop = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.mxgraph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, java.util.List, java.util.List)
           */
        public void apply(mxCellState state, mxCellState source,
                          mxCellState target, List<mxPoint> points, List<mxPoint> result) {
            if (source != null) {
                mxGraphView view = state.getView();
                mxGraph graph = view.getGraph();
                mxPoint pt = (points != null && points.size() > 0) ? points
                        .get(0) : null;

                if (pt != null) {
                    pt = view.transformControlPoint(state, pt);

                    if (source.contains(pt.getX(), pt.getY())) {
                        pt = null;
                    }
                }

                double x = 0;
                double dx = 0;
                double y = 0;
                double dy = 0;

                double seg = mxUtils.getDouble(state.getStyle(),
                        mxConstants.STYLE_SEGMENT, graph.getGridSize())
                        * view.getScale();
                String dir = mxUtils
                        .getString(state.getStyle(),
                                mxConstants.STYLE_DIRECTION,
                                mxConstants.DIRECTION_WEST);

                if (dir.equals(mxConstants.DIRECTION_NORTH)
                        || dir.equals(mxConstants.DIRECTION_SOUTH)) {
                    x = view.getRoutingCenterX(source);
                    dx = seg;
                } else {
                    y = view.getRoutingCenterY(source);
                    dy = seg;
                }

                if (pt == null || pt.getX() < source.getX()
                        || pt.getX() > source.getX() + source.getWidth()) {
                    if (pt != null) {
                        x = pt.getX();
                        dy = Math.max(Math.abs(y - pt.getY()), dy);
                    } else {
                        if (dir.equals(mxConstants.DIRECTION_NORTH)) {
                            y = source.getY() - 2 * dx;
                        } else if (dir.equals(mxConstants.DIRECTION_SOUTH)) {
                            y = source.getY() + source.getHeight() + 2 * dx;
                        } else if (dir.equals(mxConstants.DIRECTION_EAST)) {
                            x = source.getX() - 2 * dy;
                        } else {
                            x = source.getX() + source.getWidth() + 2 * dy;
                        }
                    }
                } else {
                    // pt != null
                    x = view.getRoutingCenterX(source);
                    dx = Math.max(Math.abs(x - pt.getX()), dy);
                    y = pt.getY();
                    dy = 0;
                }

                result.add(new mxPoint(x - dx, y - dy));
                result.add(new mxPoint(x + dx, y + dy));
            }
        }
    };

    /**
     * Uses either SideToSide or TopToBottom depending on the horizontal
     * flag in the cell style. SideToSide is used if horizontal is true or
     * unspecified.
     */
    public static mxEdgeStyleFunction ElbowConnector = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.mxgraph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, java.util.List, java.util.List)
           */
        public void apply(mxCellState state, mxCellState source,
                          mxCellState target, List<mxPoint> points, List<mxPoint> result) {
            mxPoint pt = (points != null && points.size() > 0) ? points.get(0)
                    : null;

            boolean vertical = false;
            boolean horizontal = false;

            if (source != null && target != null) {
                if (pt != null) {
                    double left = Math.min(source.getX(), target.getX());
                    double right = Math.max(source.getX() + source.getWidth(),
                            target.getX() + target.getWidth());

                    double top = Math.min(source.getY(), target.getY());
                    double bottom = Math.max(
                            source.getY() + source.getHeight(), target.getY()
                            + target.getHeight());

                    pt = state.getView().transformControlPoint(state, pt);

                    vertical = pt.getY() < top || pt.getY() > bottom;
                    horizontal = pt.getX() < left || pt.getX() > right;
                } else {
                    double left = Math.max(source.getX(), target.getX());
                    double right = Math.min(source.getX() + source.getWidth(),
                            target.getX() + target.getWidth());

                    vertical = left == right;

                    if (!vertical) {
                        double top = Math.max(source.getY(), target.getY());
                        double bottom = Math.min(source.getY()
                                + source.getHeight(), target.getY()
                                + target.getHeight());

                        horizontal = top == bottom;
                    }
                }
            }

            if (!horizontal
                    && (vertical || mxUtils.getString(state.getStyle(),
                    mxConstants.STYLE_ELBOW, "").equals(
                    mxConstants.ELBOW_VERTICAL))) {
                mxEdgeStyle.TopToBottom.apply(state, source, target, points,
                        result);
            } else {
                mxEdgeStyle.SideToSide.apply(state, source, target, points,
                        result);
            }
        }
    };

    /**
     * Provides a vertical elbow edge.
     */
    public static mxEdgeStyleFunction SideToSide = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.mxgraph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, java.util.List, java.util.List)
           */
        public void apply(mxCellState state, mxCellState source,
                          mxCellState target, List<mxPoint> points, List<mxPoint> result) {
            mxGraphView view = state.getView();
            mxPoint pt = ((points != null && points.size() > 0) ? points.get(0)
                    : null);
            mxPoint p0 = state.getAbsolutePoint(0);
            mxPoint pe = state
                    .getAbsolutePoint(state.getAbsolutePointCount() - 1);

            if (pt != null) {
                pt = view.transformControlPoint(state, pt);
            }

            if (p0 != null) {
                source = new mxCellState();
                source.setX(p0.getX());
                source.setY(p0.getY());
            }

            if (pe != null) {
                target = new mxCellState();
                target.setX(pe.getX());
                target.setY(pe.getY());
            }

            if (source != null && target != null) {
                double l = Math.max(source.getX(), target.getX());
                double r = Math.min(source.getX() + source.getWidth(), target
                        .getX()
                        + target.getWidth());

                double x = (pt != null) ? pt.getX() : r + (l - r) / 2;

                double y1 = view.getRoutingCenterY(source);
                double y2 = view.getRoutingCenterY(target);

                if (pt != null) {
                    if (pt.getY() >= source.getY()
                            && pt.getY() <= source.getY() + source.getHeight()) {
                        y1 = pt.getY();
                    }

                    if (pt.getY() >= target.getY()
                            && pt.getY() <= target.getY() + target.getHeight()) {
                        y2 = pt.getY();
                    }
                }

                if (!target.contains(x, y1) && !source.contains(x, y1)) {
                    result.add(new mxPoint(x, y1));
                }

                if (!target.contains(x, y2) && !source.contains(x, y2)) {
                    result.add(new mxPoint(x, y2));
                }

                if (result.size() == 1) {
                    if (pt != null) {
                        result.add(new mxPoint(x, pt.getY()));
                    } else {
                        double t = Math.max(source.getY(), target.getY());
                        double b = Math.min(source.getY() + source.getHeight(),
                                target.getY() + target.getHeight());

                        result.add(new mxPoint(x, t + (b - t) / 2));
                    }
                }
            }
        }
    };

    /**
     * Provides a horizontal elbow edge.
     */
    public static mxEdgeStyleFunction TopToBottom = new mxEdgeStyleFunction() {

        /* (non-Javadoc)
           * @see com.mxgraph.view.mxEdgeStyle.mxEdgeStyleFunction#apply(com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, com.mxgraph.view.mxCellState, java.util.List, java.util.List)
           */
        public void apply(mxCellState state, mxCellState source,
                          mxCellState target, List<mxPoint> points, List<mxPoint> result) {
            mxGraphView view = state.getView();
            mxPoint pt = ((points != null && points.size() > 0) ? points.get(0)
                    : null);
            mxPoint p0 = state.getAbsolutePoint(0);
            mxPoint pe = state
                    .getAbsolutePoint(state.getAbsolutePointCount() - 1);

            if (pt != null) {
                pt = view.transformControlPoint(state, pt);
            }

            if (p0 != null) {
                source = new mxCellState();
                source.setX(p0.getX());
                source.setY(p0.getY());
            }

            if (pe != null) {
                target = new mxCellState();
                target.setX(pe.getX());
                target.setY(pe.getY());
            }

            if (source != null && target != null) {
                double t = Math.max(source.getY(), target.getY());
                double b = Math.min(source.getY() + source.getHeight(), target
                        .getY()
                        + target.getHeight());

                double x = view.getRoutingCenterX(source);

                if (pt != null && pt.getX() >= source.getX()
                        && pt.getX() <= source.getX() + source.getWidth()) {
                    x = pt.getX();
                }

                double y = (pt != null) ? pt.getY() : b + (t - b) / 2;

                if (!target.contains(x, y) && !source.contains(x, y)) {
                    result.add(new mxPoint(x, y));
                }

                if (pt != null && pt.getX() >= target.getX()
                        && pt.getX() <= target.getX() + target.getWidth()) {
                    x = pt.getX();
                } else {
                    x = view.getRoutingCenterX(target);
                }

                if (!target.contains(x, y) && !source.contains(x, y)) {
                    result.add(new mxPoint(x, y));
                }

                if (result.size() == 1) {
                    if (pt != null) {
                        result.add(new mxPoint(pt.getX(), y));
                    } else {
                        double l = Math.max(source.getX(), target.getX());
                        double r = Math.min(source.getX() + source.getWidth(),
                                target.getX() + target.getWidth());

						result.add(new mxPoint(l + (r - l) / 2, y));
					}
				}
			}
		}
	};

}
